home *** CD-ROM | disk | FTP | other *** search
/ Aminet 5 / Aminet 5 - March 1995.iso / Aminet / text / misc / pcal_4_5.lha / pcal / pcalutil.c < prev    next >
C/C++ Source or Header  |  1994-10-16  |  25KB  |  1,125 lines

  1. /*
  2.  * pcalutil.c - utility routines for Pcal
  3.  *
  4.  * Contents:
  5.  *
  6.  *        alloc
  7.  *        calc_day
  8.  *        calc_weekday
  9.  *        calc_year_day
  10.  *        ci_strcmp
  11.  *        ci_strncmp
  12.  *        copy_text
  13.  *        cvt_escape
  14.  *        define_font
  15.  *        define_shading
  16.  *        find_executable
  17.  *        getline
  18.  *        is_valid
  19.  *        loadwords
  20.  *        mk_filespec
  21.  *        mk_path
  22.  *        normalize
  23.  *        note_box
  24.  *        note_day
  25.  *        split_date
  26.  *        trnlog
  27.  *
  28.  * Revision history:
  29.  *
  30.  *    4.5    AWR    10/01/93    add define_font() for redefining font
  31.  *                    name and/or size independently; add
  32.  *                    define_shading() (replaces old
  33.  *                    gen_shading) for date/fill shading
  34.  *
  35.  *            04/28/93    restructure function definitions so
  36.  *                    function name appears in first column
  37.  *                    (to facilitate searching for definition
  38.  *                    by name)
  39.  *
  40.  *    4.3    AWR    11/22/91    added special case to loadwords():
  41.  *                    split -<flag>"<text>" into two words
  42.  *
  43.  *                    removed octal/hex escape functionality
  44.  *                    from getline() to new routine
  45.  *                    cvt_escape() (for use elsewhere)
  46.  *
  47.  *            10/25/91    added parameters to loadwords() and
  48.  *                    getline() to avoid always using same
  49.  *                    global data
  50.  *
  51.  *            10/15/91    revised UN*X mk_filespec() to translate
  52.  *                    "~/" in path name as well as file name
  53.  *
  54.  *    4.2    AWR    10/08/91    support -[kK] flags (cf. note_box())
  55.  *
  56.  *            10/03/91    added note_box(), note_day()
  57.  *
  58.  *    4.11    AWR    08/20/91    documented find_executable()
  59.  *
  60.  *    4.02    AWR    06/07/91    added find_executable()
  61.  *
  62.  *    4.0    AWR    02/24/91    Revised getline() and copy_text() to
  63.  *                    handle C-style escapes of characters
  64.  *                    and octal/hex numbers
  65.  *
  66.  *            02/19/91    Added support for negative ordinals
  67.  *                    in calc_day(), calc_year_day()
  68.  *
  69.  *            02/04/91    Added calc_year_day()
  70.  *
  71.  *            01/15/91    Extracted from pcal.c
  72.  *
  73.  */
  74.  
  75.  
  76. /*
  77.  * Standard headers:
  78.  */
  79.  
  80. #include <stdio.h>
  81. #include <ctype.h>
  82. #include <string.h>
  83.  
  84. /*
  85.  * Pcal-specific definitions:
  86.  */
  87.  
  88. #include "pcaldefs.h"
  89. #include "pcalglob.h"
  90. #include "pcallang.h"
  91.  
  92. /*
  93.  * Macros:
  94.  */
  95.  
  96. /* skip over numeric field and subsequent non-numeric characters */
  97. #define SKIP_FIELD(p) \
  98.     do { while (*p && isdigit(*p)) p++; \
  99.          while (*p && !isdigit(*p)) p++; } while (0)
  100.  
  101. /* guarantee that a path is terminated by the END_PATH character */
  102. #define TERM_PATH(path) \
  103.     do { char *p;    \
  104.         if ((p = P_LASTCHAR(path)) && *p != END_PATH) \
  105.             *++p = END_PATH, *++p = '\0'; } while (0)
  106.  
  107. /* split string into two substrings at separator 'c' */
  108. #define SPLIT(str, str1, str2, c) \
  109.     do { char *p; \
  110.     str2 = (p = strrchr(str, c)) ? (*p = '\0', ++p) : ""; \
  111.     if (p = strchr(str1 = str, c)) *p = '\0'; } while (0)
  112.  
  113.  
  114. /*
  115.  * General-purpose utility routines
  116.  */
  117.  
  118.  
  119. /*
  120.  * alloc - interface to calloc(); terminates if unsuccessful
  121.  */
  122. char *
  123. #ifdef PROTOS
  124. alloc(int size)
  125. #else
  126. alloc(size)
  127.     int size;
  128. #endif
  129. {
  130.     char *p;
  131.  
  132.     if (size == 0)        /* not all calloc()s like null requests */
  133.         size = 1;
  134.  
  135.     if ((p = calloc(1, size)) == NULL) {
  136.         FPR(stderr, E_ALLOC_ERR, progname);
  137.         exit(EXIT_FAILURE);
  138.     }
  139.  
  140.     return p;
  141. }
  142.  
  143.  
  144. /*
  145.  * ci_str{n}cmp - case-insensitive flavors of strcmp(), strncmp()
  146.  */
  147. int
  148. #ifdef PROTOS
  149. ci_strcmp(register char *s1,
  150.       register char *s2)
  151. #else
  152. ci_strcmp(s1, s2)
  153.     register char *s1, *s2;
  154. #endif
  155. {
  156.     register char c1, c2;
  157.  
  158.     for ( ; (c1 = TOLOWER(*s1)) == (c2 = TOLOWER(*s2)); s1++, s2++)
  159.         if (c1 == '\0')
  160.             return 0;
  161.  
  162.     return c1 - c2;
  163. }
  164.  
  165.  
  166. int
  167. #ifdef PROTOS
  168. ci_strncmp(register char *s1,
  169.        register char *s2,
  170.        int n)
  171. #else
  172. ci_strncmp(s1, s2, n)
  173.     register char *s1, *s2;
  174.     int n;
  175. #endif
  176. {
  177.     register char c1, c2;
  178.  
  179.     for ( ; --n >= 0 && (c1 = TOLOWER(*s1)) == (c2 = TOLOWER(*s2)); s1++, s2++)
  180.         if (c1 == '\0')
  181.             return 0;
  182.  
  183.     return n < 0 ? 0 : c1 - c2;
  184. }
  185.  
  186.  
  187. /*
  188.  * define_font - overwrites "orig_font" with specified fields from "new_font"
  189.  * (or with "dflt_font" if "new_font" is NULL or null string)
  190.  */
  191. void
  192. #ifdef PROTOS
  193. define_font(register char *orig_font,
  194.         register char *new_font,
  195.         register char *dflt_font)
  196. #else
  197. define_font(orig_font, new_font, dflt_font)
  198.     register char *orig_font;
  199.     register char *new_font;
  200.     register char *dflt_font;
  201. #endif
  202. {
  203.     char *ofont, *nfont, *osize, *nsize, tmp1[STRSIZ], tmp2[STRSIZ];
  204.  
  205.     /* use default font/size if new font is null */
  206.  
  207.     if (new_font == NULL || new_font[0] == '\0') {
  208.         strcpy(orig_font, dflt_font);
  209.         return;
  210.     }
  211.  
  212.     /* split old and new fonts into font/size components */
  213.     SPLIT(orig_font, ofont, osize, '/');
  214.  
  215.     new_font = strcpy(tmp1, new_font);
  216.     SPLIT(new_font, nfont, nsize, '/');
  217.  
  218.     /* use default size if new font size invalid */
  219.     if (nsize[0] && atoi(nsize) <= 0)
  220.         nsize = strrchr(dflt_font, '/') + 1;
  221.  
  222.     /* replace fields of old font with specified fields from new */
  223.  
  224.     strcpy(tmp2, nfont[0] ? nfont : ofont);
  225.     strcat(tmp2, "/");
  226.     strcat(tmp2, nsize[0] ? nsize : osize);
  227.  
  228.     strcpy(orig_font, tmp2);
  229. }
  230.  
  231.  
  232. /*
  233.  * define_shading - overwrites "orig_shading" with specified fields from
  234.  * "new_shading" (or with "dflt_shading" if "new_shading" is NULL or null
  235.  * string)
  236.  */
  237.  
  238. void
  239. #ifdef PROTOS
  240. define_shading(register char *orig_shading,
  241.            register char *new_shading,
  242.            register char *dflt_shading)
  243. #else
  244. define_shading(orig_shading, new_shading, dflt_shading)
  245.     register char *orig_shading;
  246.     register char *new_shading;
  247.     register char *dflt_shading;
  248. #endif
  249. {
  250.     char *odate, *ndate, *ddate, *ofill, *nfill, *dfill;
  251.     char tmp1[STRSIZ], tmp2[STRSIZ], tmp3[STRSIZ];
  252.     double v;
  253.  
  254.     /* use default date/fill if new _shading is null */
  255.     if (new_shading == NULL || new_shading[0] == '\0') {
  256.         strcpy(orig_shading, dflt_shading);
  257.         return;
  258.     }
  259.  
  260.     /* split old, new, and default shadings into date/fill components */
  261.     SPLIT(orig_shading, odate, ofill, '/');
  262.  
  263.     new_shading = strcpy(tmp1, new_shading);
  264.     SPLIT(new_shading, ndate, nfill, '/');
  265.  
  266.     dflt_shading = strcpy(tmp2, dflt_shading);
  267.     SPLIT(dflt_shading, ddate, dfill, '/');
  268.  
  269.     /* replace invalid fields from new shading with default values */
  270.     if (nfill[0] && strchr(nfill, RGB_CHAR) == NULL &&
  271.         ((v = atof(nfill)) <= 0.0 || v > 1.0))
  272.         nfill = dfill;
  273.     if (ndate[0] && strchr(ndate, RGB_CHAR) == NULL &&
  274.         ((v = atof(ndate)) <= 0.0 || v > 1.0))
  275.         ndate = ddate;
  276.  
  277.     /* replace fields of old shading with specified fields from new */
  278.  
  279.     strcpy(tmp3, ndate[0] ? ndate : odate);
  280.     strcat(tmp3, "/");
  281.     strcat(tmp3, nfill[0] ? nfill : ofill);
  282.  
  283.     strcpy(orig_shading, tmp3);
  284. }
  285.  
  286. /*
  287.  * Date calculation routines (see also macros in pcaldefs.h)
  288.  */
  289.  
  290.  
  291. /*
  292.  * normalize - adjust day in case it has crossed month (or year) bounds 
  293.  */
  294. void
  295. #ifdef PROTOS
  296. normalize(DATE *pd)
  297. #else
  298. normalize(pd)
  299.     DATE *pd;        /* pointer to date */
  300. #endif
  301. {
  302.     int len;
  303.  
  304.     /* adjust if day is in previous or following month */
  305.  
  306.     while (pd->dd < 1) {
  307.         pd->yy = PREV_YEAR(pd->mm, pd->yy);
  308.         pd->mm = PREV_MONTH(pd->mm, pd->yy);
  309.         pd->dd += LENGTH_OF(pd->mm, pd->yy);
  310.     }
  311.  
  312.     while (pd->dd > (len = LENGTH_OF(pd->mm, pd->yy))) {
  313.         pd->dd -= len;
  314.         pd->yy = NEXT_YEAR(pd->mm, pd->yy);
  315.         pd->mm = NEXT_MONTH(pd->mm, pd->yy);
  316.     }
  317. }
  318.  
  319.  
  320. /*
  321.  * calc_day - calculate calendar date from ordinal date (e.g., "first Friday
  322.  * in November", "last day in October"); return calendar date if it exists, 
  323.  * 0 if it does not
  324.  */
  325. int
  326. #ifdef PROTOS
  327. calc_day(int ord,
  328.      int wkd,
  329.      int mm)
  330. #else
  331. calc_day(ord, wkd, mm)
  332.     int ord;
  333.     int wkd;
  334.     int mm;
  335. #endif
  336. {
  337. #ifdef PROTOS
  338.     int first, last, day, (*pfcn)(int, int, int);
  339. #else
  340.     int first, last, day, (*pfcn)();
  341. #endif
  342.  
  343.     if (IS_WILD(wkd)) {    /* "day", "weekday", "workday", or "holiday" */
  344.         pfcn = pdatefcn[wkd - WILD_FIRST];
  345.         last = LENGTH_OF(mm, curr_year);
  346.  
  347.         if (ord < 0) {            /* search backwards */
  348.             for (day = last; 
  349.                  day >= 1 &&
  350.                 !((*pfcn)(mm, day, curr_year) && ++ord == 0);
  351.                  day--)
  352.                 ;
  353.         } else {            /* search forwards */
  354.             for (day = 1; 
  355.                  day <= last && 
  356.                 !((*pfcn)(mm, day, curr_year) && --ord == 0);
  357.                  day++)    
  358.                 ;
  359.         }
  360.         return is_valid(mm, day, curr_year) ? day : 0; 
  361.  
  362.     } else {        /* fixed weekday - calculate it */
  363.         first = (wkd - FIRST_OF(mm, curr_year) + 7) % 7 + 1;
  364.         if (ord < 0) {        /* get last (try 5th, then 4th) */
  365.             if (!is_valid(mm, last = first + 28, curr_year))
  366.                 last -= 7;
  367.             if (!is_valid(mm, day = last + 7 * (ord + 1),
  368.                 curr_year))
  369.                 day = 0;    
  370.         }
  371.         else 
  372.             if (!is_valid(mm, day = first + 7 * (ord - 1),
  373.                 curr_year))
  374.                 day = 0;
  375.  
  376.         return day;
  377.     }
  378.  
  379. }
  380.  
  381.  
  382. /*
  383.  * calc_year_day - calculate calendar date from ordinal date within year
  384.  * (e.g., "last Friday in year", "10th holiday in year"); if date exists,
  385.  * fill in pdate and return TRUE; else return FALSE
  386.  */
  387. #ifdef PROTOS
  388. calc_year_day(int ord,
  389.           int wkd,
  390.           DATE *pdate)
  391. #else
  392. calc_year_day(ord, wkd, pdate)
  393.     int ord;
  394.     int wkd;
  395.     DATE *pdate;
  396. #endif
  397. {
  398. #ifdef PROTOS
  399.     int incr, (*pfcn)(int, int, int);
  400. #else
  401.     int incr, (*pfcn)();
  402. #endif
  403.     DATE date;
  404.  
  405.     if (IS_WILD(wkd)) {    /* "day", "weekday", "workday", or "holiday" */
  406.         pfcn = pdatefcn[wkd - WILD_FIRST];
  407.  
  408.         if (ord < 0) {            /* nth occurrence backwards */
  409.             MAKE_DATE(date, DEC, 31, curr_year);
  410.             ord = -ord;
  411.             incr = -1;
  412.         } else {            /* nth occurrence forwards */
  413.             MAKE_DATE(date, JAN, 1, curr_year);
  414.             incr = 1;
  415.         }
  416.  
  417.         /* search for selected occurrence of specified wildcard */
  418.  
  419.         while (date.yy == curr_year &&
  420.                !((*pfcn)(date.mm, date.dd, date.yy) && --ord == 0)) {
  421.             date.dd += incr;
  422.             normalize(&date);
  423.         }
  424.  
  425.     } else {        /* fixed weekday - calculate it */
  426.         if (ord < 0)
  427.             MAKE_DATE(date, DEC,
  428.                   calc_day(-1, wkd, DEC) + 7 * (ord + 1),
  429.                   curr_year);
  430.         else
  431.             MAKE_DATE(date, JAN,
  432.                   calc_day(1, wkd, JAN) + 7 * (ord - 1),
  433.                   curr_year);
  434.         normalize(&date);
  435.     }
  436.  
  437.     return date.yy == curr_year ? (*pdate = date, TRUE) : FALSE;
  438. }
  439.  
  440.  
  441. /*
  442.  * calc_weekday - return the weekday (0-6) of mm/dd/yy (mm: 1-12)
  443.  */
  444. int
  445. #ifdef PROTOS
  446. calc_weekday(int mm,
  447.          int dd,
  448.          int yy)
  449. #else
  450. calc_weekday(mm, dd, yy)
  451.     int mm;
  452.     int dd;
  453.     int yy;
  454. #endif
  455. {
  456.     return (yy + (yy-1)/4 - (yy-1)/100 + (yy-1)/400 + OFFSET_OF(mm, yy) +
  457.         (dd-1)) % 7;
  458. }
  459.  
  460.  
  461. /*
  462.  * note_day - translate n (from "note/<n>" spec) to appropriate note text
  463.  * day for mm/yy; return note text day if in range, 0 if not
  464.  */
  465. int
  466. #ifdef PROTOS
  467. note_day(int mm,
  468.      int n,
  469.      int yy)
  470. #else
  471. note_day(mm, n, yy)
  472.     int mm;
  473.     int n;
  474.     int yy;
  475. #endif
  476. {
  477.     int day, lastday;
  478.  
  479.     if (n == 0)            /* convert 0 to appropriate default */
  480.         n = NOTE_DEFAULT;
  481.  
  482.     /* lastday depends on month length and presence (but not position) of
  483.      * small calendars
  484.      */
  485.     lastday = LAST_NOTE_DAY - (LENGTH_OF(mm, yy) - 28) -
  486.             (small_cal_pos == SC_NONE ? 0 : 2);
  487.  
  488.     /* count forward if n is positive, backward if negative */
  489.     day = (n > 0) ? FIRST_NOTE_DAY + n - 1 : lastday + n + 1;
  490.  
  491.     /* make sure result is valid for this month/year */
  492.     return (day >= FIRST_NOTE_DAY && day <= lastday) ? day : 0;
  493. }
  494.  
  495.  
  496. /*
  497.  * note_box - translate dd from note text day to box number for mm/yy,
  498.  * adjusting for presence and position of small calendars
  499.  */
  500. int
  501. #ifdef PROTOS
  502. note_box(int mm,
  503.      int dd,
  504.      int yy)
  505. #else
  506. note_box(mm, dd, yy)
  507.     int mm;
  508.     int dd;
  509.     int yy;
  510. #endif
  511. {
  512.     int startbox = START_BOX(mm, yy), pc, nc;
  513.  
  514.     /* move starting box to second row if conflict with small calendars */
  515.     if ((pc = prev_cal_box[small_cal_pos]) == startbox ||
  516.         (nc = next_cal_box[small_cal_pos]) == startbox)
  517.         startbox += 7;
  518.  
  519.     dd -= FIRST_NOTE_DAY;        /* convert to note box number 0..13 */
  520.     dd += (pc == 0) + (nc == 1);    /* adjust for small calendars in 0, 1 */
  521.  
  522.     /* position box after calendar body if no room before */
  523.     return dd < startbox ? dd : dd + LENGTH_OF(mm, yy);
  524. }
  525.  
  526.  
  527. /*
  528.  * is_valid - return TRUE if m/d/y is a valid date
  529.  */
  530. int
  531. #ifdef PROTOS
  532. is_valid(register int m,
  533.      register int d,
  534.      register int y)
  535. #else
  536. is_valid(m, d, y)
  537.     register int m, d, y;
  538. #endif
  539. {
  540.     return m >= JAN && m <= DEC && 
  541.         d >= 1 && d <= LENGTH_OF(m, y);
  542. }
  543.  
  544.  
  545.  
  546. /*
  547.  * Token parsing/remerging routines:
  548.  */
  549.  
  550.  
  551. /*
  552.  * loadwords - tokenize buffer 'buf' into array 'words' and return word count;
  553.  * differs from old loadwords() in that it handles quoted (" or ') strings
  554.  * and removes escaped quotes
  555.  */
  556. int
  557. #ifdef PROTOS
  558. loadwords(char **words,
  559.       char *buf)
  560. #else
  561. loadwords(words, buf)
  562.     char **words;
  563.     char *buf;
  564. #endif
  565. {
  566.     register char *ptok;
  567.     char *delim, **ap, *p1, *p2, c;
  568.     int nwords;
  569.  
  570.     for (ptok = buf, ap = words; TRUE; ap++) {
  571.  
  572.         ptok += strspn(ptok, WHITESPACE); /* find next token */
  573.  
  574.         if (! *ptok) {            /* end of buf? */
  575.             *ap = NULL;        /* add null ptr at end */
  576.             nwords = ap - words;    /* number of non-null ptrs */
  577.             break;            /* exit loop */
  578.             }
  579.  
  580.         delim =    *ptok == '"'  ? "\"" :    /* set closing delimiter */
  581.             *ptok == '\'' ? "'"  :
  582.             WHITESPACE;
  583.  
  584.         /* split flag followed by quoted string into two words */
  585.         if (*ptok == '-' && isalpha(ptok[1]) &&
  586.             ((c = ptok[2]) == '"' || c == '\'')) {
  587.             delim = c == '"' ? "\"" : "'";
  588.             *ap++ = ptok;
  589.             ptok[2] = '\0';        /* terminate first token */
  590.             ptok += 3;        /* start second token */
  591.         }
  592.         else if (*ptok == *delim)
  593.             ptok++;            /* skip opening quote */
  594.  
  595.         *ap = ptok;            /* save token ptr */
  596.  
  597.         /* find first unescaped string delimiter - handle special
  598.          * case where preceding backslash is itself escaped
  599.          */
  600.         do {
  601.             ptok += strcspn(ptok, delim);
  602.             if ((c = ptok[-1]) == '\\')
  603.                 if (ptok[-2] == '\\')
  604.                     break;    /* stop on \\" or \\' */
  605.                 else
  606.                     ptok++;
  607.         } while (c == '\\');
  608.  
  609.         if (*ptok)            /* terminate token */
  610.             *ptok++ = '\0';
  611.     }
  612.  
  613.     /* now reprocess the word list, removing remaining escapes */
  614.     for (ap = words; *ap; ap++)
  615.         for (p1 = p2 = *ap; c = *p2 = *p1++; *p2++)
  616.             if (c == '\\')
  617.                 *p2 = *p1++;
  618.  
  619.     return nwords;                /* return word count */
  620.  
  621. }
  622.  
  623.  
  624. /*
  625.  * copy_text - retrieve remaining text in pbuf and copy to output string,
  626.  * separating tokens by a single blank and condensing runs of blanks (all
  627.  * other whitespace has been converted to blanks by now) to one blank
  628.  */
  629. void
  630. #ifdef PROTOS
  631. copy_text(char *pbuf,        /* output buffer - can be pbuf itself */
  632.       char **ptext)        /* pointer to first text word in "words" */
  633. #else
  634. copy_text(pbuf, ptext)
  635.     char *pbuf;        /* output buffer - can be pbuf itself */
  636.     char **ptext;        /* pointer to first text word in "words" */
  637. #endif
  638. {
  639.     char *p, *pb;
  640.  
  641.     /* copy words to pbuf, separating by one blank */
  642.  
  643.     for (*(pb = pbuf) = '\0'; p = *ptext; *pb++ = *++ptext ? ' ' : '\0') {
  644.         for ( ; *p; *p++)
  645.             if (! (*p == ' ' && (pb == pbuf || pb[-1] == ' ')))
  646.                 *pb++ = *p;
  647.         if (pb > pbuf && pb[-1] == ' ')
  648.             pb--;
  649.     }
  650. }
  651.  
  652.  
  653. /*
  654.  * split_date - extract 1-3 numeric fields (separated by one or more
  655.  * non-numeric characters) from date string; return number of fields
  656.  */
  657. int
  658. #ifdef PROTOS
  659. split_date(char *pstr,
  660.        int *pn1,
  661.        int *pn2,
  662.        int *pn3)
  663. #else
  664. split_date(pstr, pn1, pn2, pn3)
  665.     char *pstr;            /* input string */
  666.     int *pn1, *pn2, *pn3;        /* output numbers */
  667. #endif
  668. {
  669.     int i, n, *pn;
  670.  
  671.     /* attempt to extract up to three numeric fields */
  672.     for (n = 0, i = 1; i <= 3; i++) {
  673.         pn = i == 1 ? pn1 : i == 2 ? pn2 : pn3;    /* crude but portable */
  674.         if (pn)
  675.             *pn = *pstr ? (n++, atoi(pstr)) : 0;
  676.         SKIP_FIELD(pstr);        /* go to next field */
  677.     }
  678.  
  679.     return n;
  680. }
  681.  
  682.  
  683.  
  684. /*
  685.  * Escape sequence conversion routines:
  686.  */
  687.  
  688.  
  689. /*
  690.  * octal_esc - read up to 3 octal digits from character string; fill in
  691.  * value of octal constant and return pointer to last character
  692.  */
  693. static char *
  694. #ifdef PROTOS
  695. octal_esc(char *buf,
  696.       char *val)
  697. #else
  698. octal_esc(buf, val)
  699.     char *buf;
  700.     char *val;
  701. #endif
  702. {
  703.     int i, n, c;
  704.  
  705.     for (n = 0, i = 0; i < 3 && (c = buf[i]) && isodigit(c); i++) {
  706.         n = n * 8 + (c - '0');
  707.     }
  708.  
  709.     *val = n & CHAR_MSK;    /* fill in the value */
  710.     return buf + i - 1;    /* return pointer to last character */
  711. }
  712.  
  713.  
  714. /*
  715.  * hex_esc - read 'x' or 'X' followed by 1 or 2 hex digits from character
  716.  * string; fill in value of hexadecimal constant (or letter if no hex digits
  717.  * follow) and return pointer to last character
  718.  */
  719. static char *
  720. #ifdef PROTOS
  721. hex_esc(char *buf,
  722.     char *val)
  723. #else
  724. hex_esc(buf, val)
  725.     char *buf;
  726.     char *val;
  727. #endif
  728. {
  729.     int i, n, c;
  730.  
  731.     /* assume leading character is known to be 'x' or 'X'; skip it */
  732.     buf++;
  733.  
  734.     for (n = 0, i = 0; i < 2 && (c = buf[i]) && isxdigit(c); i++) {
  735.         n = n * 16 + (isupper(c) ? c - 'A' + 10 :
  736.                   islower(c) ? c - 'a' + 10 :
  737.                   c - '0');
  738.     }
  739.  
  740.     *val = i == 0 ? buf[-1] : n & CHAR_MSK; /* fill in the value */
  741.     return buf + i - 1;        /* return pointer to last character */
  742. }
  743.  
  744.  
  745. /*
  746.  * cvt_escape - copy string ibuf to string obuf, converting octal/hex/whitespace
  747.  * escape sequences to the appropriate equivalents.  Escaped quotes and
  748.  * backslashes are not converted here; they need to be preserved so that
  749.  * loadwords() can parse quoted strings correctly
  750.  */
  751. void
  752. #ifdef PROTOS
  753. cvt_escape(char *obuf,
  754.        char *ibuf)
  755. #else
  756. cvt_escape(obuf, ibuf)
  757.     char *obuf, *ibuf;
  758. #endif
  759. {
  760.     char c, c2, *po, *pi;
  761.     static char whitespace[] = "abfnrtv";    /* cf. ANSI spec, 2.2.2 */
  762.     static char no_cvt[] = "\"'\\";
  763.  
  764.     for (po = obuf, pi = ibuf; c = *pi; *po++ = c, pi++) {
  765.         /* handle escape sequences here: escaped whitespace
  766.          * and ANSI escapes are all converted to a space;
  767.          * octal and hex constants are converted in place;
  768.          * escaped single/double quotes are left escaped for
  769.          * loadwords() to handle later on
  770.          */
  771.         if (c == '\\') {
  772.             c2 = *++pi;
  773.             if (isspace(c2) || strchr(whitespace, c2)) {
  774.                 c = ' ';
  775.             }
  776.             else if (isodigit(c2)) {    /* octal */    
  777.                 pi = octal_esc(pi, &c);
  778.             }
  779.             else if (TOLOWER(c2) == 'x') {    /* hex */
  780.                 pi = hex_esc(pi, &c);
  781.             }
  782.             else {
  783.                 if (strchr(no_cvt, c2))    /* leave escaped */
  784.                     *po++ = c;
  785.                 c = c2;
  786.             }
  787.         }
  788.     }
  789.  
  790.     *po = '\0';
  791. }
  792.  
  793.  
  794.  
  795. /*
  796.  * File input routines:
  797.  */
  798.  
  799.  
  800. /*
  801.  * getline - read next non-null line of input file into buf; return 0 on EOF.
  802.  * Strip leading whitespace and trailing comments; handle escaped newlines
  803.  * and call cvt_escape() to translate other escape sequences
  804.  */
  805. int
  806. #ifdef PROTOS
  807. getline(FILE *fp,
  808.     char *buf,
  809.     int *pline)
  810. #else
  811. getline(fp, buf, pline)
  812.     FILE *fp;
  813.     char *buf;
  814.     int *pline;
  815. #endif
  816. {
  817.     register char *cp;
  818.     register int c, c2;
  819.     int in_comment;        /* comments: from '#' to end-of-line */
  820.     char tmpbuf[LINSIZ];    /* temporary buffer to accumulate line */
  821.  
  822.     cp = tmpbuf;
  823.     *buf = '\0';        /* in case of premature EOF */
  824.  
  825.     do {
  826.         in_comment = FALSE;
  827.         while ((c = getc(fp)) != '\n' && c != EOF) {
  828.             if (c == COMMENT_CHAR)
  829.                 in_comment = TRUE;
  830.  
  831.             if (isspace(c))        /* whitespace => blank */
  832.                 c = ' ';
  833.  
  834.             /* ignore comments and leading white space */
  835.             if (in_comment || (cp == tmpbuf && c == ' '))
  836.                 continue;
  837.  
  838.             /* only handle escape newlines and '#' here; other
  839.              * escapes are now handled by cvt_escape() or
  840.              * loadwords() (q.v.)
  841.              */
  842.             if (c == '\\') {
  843.                 if ((c2 = getc(fp)) == EOF)
  844.                     return FALSE;
  845.  
  846.                 if (c2 == '\n') {
  847.                     c = ' ';
  848.                     (*pline)++;
  849.                 }
  850.                 else if (c2 == COMMENT_CHAR)
  851.                     c = '#';
  852.                 else
  853.                     ungetc(c2, fp);
  854.             }
  855.             *cp++ = c;
  856.         }
  857.  
  858.         if (c == EOF)            /* no more input lines */
  859.             return FALSE;
  860.  
  861.         (*pline)++;            /* bump line number */
  862.  
  863.     } while (cp == tmpbuf);            /* ignore empty lines */
  864.  
  865.     *cp = '\0';
  866.     cvt_escape(buf, tmpbuf);        /* convert escape sequences */
  867.     return TRUE;
  868. }
  869.  
  870.  
  871. /*
  872.  * Routines dealing with translation of file specifications (VMS, Un*x)
  873.  */
  874.  
  875. #ifdef VMS
  876. /*
  877.  * mk_path - extract the path component from VMS file spec
  878.  */
  879. char *
  880. #ifdef PROTOS
  881. mk_path(char *path,        /* output path */
  882.     char *filespec)        /* input filespec */
  883. #else
  884. mk_path(path, filespec)
  885.     char *path;        /* output path */
  886.     char *filespec;        /* input filespec */
  887. #endif
  888. {
  889.     char *p;
  890.  
  891.     strcpy(path, filespec);
  892.     if (!(p = strchr(path, ']')) && !(p = strchr(path, ':')))
  893.         p = path - 1;    /* return null string if no path */
  894.     *++p = '\0';
  895.  
  896.     return path;
  897. }
  898.  
  899.  
  900. /*
  901.  * mk_filespec - merge VMS path and file names, where latter can be relative
  902.  */
  903. char *
  904. #ifdef PROTOS
  905. mk_filespec(char *filespec,    /* output filespec */
  906.         char *path,        /* input path */
  907.         char *name)        /* input file name */
  908. #else
  909. mk_filespec(filespec, path, name)
  910.     char *filespec;        /* output filespec */
  911.     char *path;        /* input path */
  912.     char *name;        /* input file name */
  913. #endif
  914. {
  915.     char *p;
  916.  
  917.     *filespec = '\0';
  918.  
  919.     /* copy name intact if absolute; else merge path and relative name */
  920.     if (!strchr(name, ':')) {
  921.         strcpy(filespec, path);
  922.         if ((p = P_LASTCHAR(filespec)) && *p == END_PATH &&
  923.             name[0] == START_PATH && strchr(".-", name[1]))
  924.             *p = *++name == '-' ? '.' : '\0';
  925.     }
  926.  
  927.     return strcat(filespec, name);
  928. }
  929.  
  930.  
  931. /*
  932.  * trnlog - return translation of VMS logical name (null if missing)
  933.  */
  934. char *
  935. #ifdef PROTOS
  936. trnlog(char *logname)
  937. #else
  938. trnlog(logname)
  939.     char *logname;
  940. #endif
  941. {
  942.     static char trnbuf[STRSIZ];
  943.     
  944.     $DESCRIPTOR(src, logname);
  945.     $DESCRIPTOR(dst, trnbuf);
  946.     short len;
  947.     int ret;
  948.     
  949.     src.dsc$w_length  = strlen(logname);
  950.     ret = LIB$SYS_TRNLOG(&src, &len, &dst);
  951.     return ret == SS$_NORMAL ? (trnbuf[len] = '\0', trnbuf) : NULL;
  952. }
  953.  
  954. #else        /* apparently DOS and Amiga can use the Un*x flavors */
  955.  
  956. /*
  957.  * mk_path - extract the path component from a Un*x file spec
  958.  */
  959. char *
  960. #ifdef PROTOS
  961. mk_path(char *path,        /* output path */
  962.     char *filespec)        /* input filespec */
  963. #else
  964. mk_path(path, filespec)
  965.     char *path;        /* output path */
  966.     char *filespec;        /* input filespec */
  967. #endif
  968. {
  969.     char *p;
  970.  
  971.     strcpy(path, filespec);
  972.     if (! (p = strrchr(path, END_PATH)) )
  973.         p = path - 1;    /* return null string if no path */
  974.  
  975.     *++p = '\0';
  976.     return path;
  977. }
  978.  
  979.  
  980. /*
  981.  * mk_filespec - merge Un*x path and file names, where latter can be relative
  982.  */
  983. char *
  984. #ifdef PROTOS
  985. mk_filespec(char *filespec,    /* output filespec */
  986.         char *path,        /* input path */
  987.         char *name)        /* input file name */
  988. #else
  989. mk_filespec(filespec, path, name)
  990.     char *filespec;        /* output filespec */
  991.     char *path;        /* input path */
  992.     char *name;        /* input file name */
  993. #endif
  994. {
  995.     char *p;
  996.  
  997.     *filespec = '\0';
  998.  
  999.     /* copy name intact if absolute; else merge path and relative name */
  1000.  
  1001.     /* if name starts with "~/", translate it for user */
  1002.     if (strncmp(name, "~/", 2) == 0 && (p = trnlog(HOME_DIR)) != NULL) {
  1003.         strcpy(filespec, p);
  1004.         TERM_PATH(filespec);
  1005.         name += 2;        /* skip "~/" */
  1006.     }
  1007.     else if (*name != START_PATH) {        /* relative path */
  1008.         /* if path starts with "~/", translate it for user */
  1009.         if (strncmp(path, "~/", 2) == 0 &&
  1010.             (p = trnlog(HOME_DIR)) != NULL) {
  1011.             strcpy(filespec, p);
  1012.             TERM_PATH(filespec);
  1013.             path += 2;    /* skip "~/"; append rest below */
  1014.         }
  1015.         strcat(filespec, path);
  1016.         TERM_PATH(filespec);
  1017.     }
  1018.  
  1019.     return strcat(filespec, name);
  1020. }
  1021.  
  1022.  
  1023. /*
  1024.  * trnlog - return translation of Un*x environment variable
  1025.  */
  1026. char *
  1027. #ifdef PROTOS
  1028. trnlog(char *logname)
  1029. #else
  1030. trnlog(logname)
  1031.     char *logname;
  1032. #endif
  1033. {
  1034.     return getenv(logname);
  1035. }
  1036.  
  1037. #endif
  1038.  
  1039. #ifdef UN_X    /* highly Un*x-dependent; probably nobody else can use it */
  1040.  
  1041. #include <sys/types.h>
  1042. #include <sys/stat.h>
  1043.  
  1044. /* X_OK is a #define'd constant used by access() to determine whether or not
  1045.  * the specified file is executable by the user.  Investigation of several
  1046.  * Un*x systems reveals that 01 appears to be the standard value, but it would
  1047.  * be best to #include the appropriate system .h file instead of hard-coding
  1048.  * the value (as below).  (The hard-coded approach was adopted reluctantly
  1049.  * because there does not appear to be a standardized location for X_OK: some
  1050.  * systems put it in <unistd.h>, others in <access.h>, and others elsewhere
  1051.  * (if at all).  The access() man page may be helpful.)  (AWR 08/20/91)
  1052.  */
  1053. #define X_OK    01        /* does user have execute permission? */
  1054.  
  1055. /*
  1056.  * find_executable - return full path name of executable
  1057.  */
  1058. char *
  1059. #ifdef PROTOS
  1060. find_executable(char *prog)
  1061. #else
  1062. find_executable(prog)
  1063.     char *prog;
  1064. #endif
  1065. {
  1066.     char pathvar[1000], *p, *pnext;
  1067.     struct stat st;
  1068.     static char filepath[STRSIZ];
  1069.     
  1070.     /* if 'prog' is an absolute/relative path name or environment
  1071.      * variable 'PATH' does not exist, return 'prog'; otherwise,
  1072.      * search the directories specified in 'PATH' for the executable
  1073.      */
  1074.     if (strchr(prog, END_PATH) == 0 && (p = getenv(PATH_ENV_VAR)) != 0) {
  1075.         strcpy(pathvar, p);
  1076.  
  1077.         /* assumes PATH is of form "dir1:dir2:...:dirN"; strtok()
  1078.          * would be handy here, but not everybody has it yet
  1079.          */
  1080.         for (p = pathvar; *p; p = pnext) {
  1081.             if ((pnext = strchr(p, ':')) != NULL)
  1082.                 *pnext++ = '\0';
  1083.             else
  1084.                 pnext = p + strlen(p);
  1085.  
  1086.             /* assume the executable lives in the path, and
  1087.              * construct its complete file name
  1088.              */
  1089.             filepath[0] = '\0';
  1090.             if (strcmp(p, ".") != 0) {
  1091.                 strcat(filepath, p);
  1092.                 strcat(filepath, "/");
  1093.             }
  1094.             strcat(filepath, prog);
  1095.  
  1096.             /* check that file a) exists, b) is a "normal" file,
  1097.              * and c) is executable - if so, return its full path
  1098.              */
  1099.             if (stat(filepath, &st) >= 0 &&
  1100.                (st.st_mode & S_IFMT) == S_IFREG &&
  1101.                     access(filepath, X_OK) == 0)
  1102.                 return filepath;
  1103.         }
  1104.     }
  1105.  
  1106.     return prog;
  1107. }
  1108. #else
  1109.  
  1110.  
  1111. /*
  1112.  * find_executable - return full path name of executable
  1113.  */
  1114. char *
  1115. #ifdef PROTOS
  1116. find_executable(char *prog)
  1117. #else
  1118. find_executable(prog)
  1119.     char *prog;
  1120. #endif
  1121. {
  1122.     return prog;    /* non-Un*x flavor always returns its argument */
  1123. }
  1124. #endif
  1125.